package org.sergeys.library.swing.treetable;

// modified by svs

/*
 * Copyright 1997-1999 Sun Microsystems, Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following
 * conditions are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * - Redistribution in binary form must reproduce the above
 *   copyright notice, this list of conditions and the following
 *   disclaimer in the documentation and/or other materials
 *   provided with the distribution.
 *
 * Neither the name of Sun Microsystems, Inc. or the names of
 * contributors may be used to endorse or promote products derived
 * from this software without specific prior written permission.
 *
 * This software is provided "AS IS," without a warranty of any
 * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
 * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
 * EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY
 * DAMAGES OR LIABILITIES SUFFERED BY LICENSEE AS A RESULT OF OR
 * RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THIS SOFTWARE OR
 * ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE
 * FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT,
 * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
 * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF
 * THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS
 * BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 *
 * You acknowledge that this software is not designed, licensed or
 * intended for use in the design, construction, operation or
 * maintenance of any nuclear facility.
 */

import java.lang.reflect.*;
import javax.swing.tree.*;

/**
 * An implementation of TreeTableModel that uses reflection to answer TableModel
 * methods. This works off a handful of values. A TreeNode is used to answer all
 * the TreeModel related methods (similiar to AbstractTreeTableModel and
 * DefaultTreeModel). The column names are specified in the constructor. The
 * values for the columns are dynamically obtained via reflection, you simply
 * provide the method names. The methods used to set a particular value are also
 * specified as an array of method names, a null method name, or null array
 * indicates the column isn't editable. And the class types, used for the
 * TableModel method getColumnClass are specified in the constructor.
 *
 * @author Scott Violet
 */
public class DynamicTreeTableModel extends AbstractTreeTableModel {
    /** Names of the columns, used for the TableModel getColumnName method. */
    private String[] columnNames;
    /**
     * Method names used to determine a particular value. Used for the
     * TableModel method getValueAt.
     */
    private String[] methodNames;
    /**
     * Setter method names, used to set a particular value. Used for the
     * TableModel method setValueAt. A null entry, or array, indicates the
     * column is not editable.
     */
    private String[] setterMethodNames;
    /** Column classes, used for the TableModel method getColumnClass. */
    @SuppressWarnings("rawtypes")
    private Class[] cTypes;

    private int[] columnWidth;

    /**
     * Constructor for creating a DynamicTreeTableModel.
     */
    @SuppressWarnings("rawtypes")
    public DynamicTreeTableModel(TreeNode root, String[] columnNames,
            String[] getterMethodNames, String[] setterMethodNames,
            Class[] cTypes, int[] columnWidth) {
        super(root);
        this.columnNames = columnNames;
        this.methodNames = getterMethodNames;
        this.setterMethodNames = setterMethodNames;
        this.cTypes = cTypes;
        this.columnWidth = columnWidth;
    }

    //
    // TreeModel interface
    //

    /**
     * TreeModel method to return the number of children of a particular node.
     * Since <code>node</code> is a TreeNode, this can be answered via the
     * TreeNode method <code>getChildCount</code>.
     */
    public int getChildCount(Object node) {
        return ((TreeNode) node).getChildCount();
    }

    /**
     * TreeModel method to locate a particular child of the specified node.
     * Since <code>node</code> is a TreeNode, this can be answered via the
     * TreeNode method <code>getChild</code>.
     */
    public Object getChild(Object node, int i) {
        return ((TreeNode) node).getChildAt(i);
    }

    /**
     * TreeModel method to determine if a node is a leaf. Since
     * <code>node</code> is a TreeNode, this can be answered via the TreeNode
     * method <code>isLeaf</code>.
     */
    public boolean isLeaf(Object node) {
        return ((TreeNode) node).isLeaf();
    }

    //
    // The TreeTable interface.
    //

    /**
     * Returns the number of column names passed into the constructor.
     */
    public int getColumnCount() {
        return columnNames.length;
    }

    /**
     * Returns the column name passed into the constructor.
     */
    public String getColumnName(int column) {
        if (cTypes == null || column < 0 || column >= cTypes.length) {
            return null;
        }
        return columnNames[column];
    }

    /**
     * Returns the column class for column <code>column</code>. This is set in
     * the constructor.
     */
    @SuppressWarnings("rawtypes")
    public Class getColumnClass(int column) {
        if (cTypes == null || column < 0 || column >= cTypes.length) {
            return null;
        }
        return cTypes[column];
    }

    /**
     * Returns the value for the column <code>column</code> and object
     * <code>node</code>. The return value is determined by invoking the method
     * specified in constructor for the passed in column.
     */
    public Object getValueAt(Object node, int column) {
        try {
            Method method = node.getClass().getMethod(methodNames[column],
                    (Class<?>[]) null);
            if (method != null) {
                return method.invoke(node, (Object[]) null);
            }
        } catch (Throwable th) {
        }

        return null;
    }

    /**
     * Returns true if there is a setter method name for column
     * <code>column</code>. This is set in the constructor.
     */
    public boolean isCellEditable(Object node, int column) {
        return (setterMethodNames != null && setterMethodNames[column] != null);
    }

    /**
     * Sets the value to <code>aValue</code> for the object <code>node</code> in
     * column <code>column</code>. This is done by using the setter method name,
     * and coercing the passed in value to the specified type.
     */
    // Note: This looks up the methods each time! This is rather inefficient;
    // it should really be changed to cache matching methods/constructors
    // based on <code>node</code>'s class, and <code>aValue</code>'s class.
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public void setValueAt(Object aValue, Object node, int column) {
        boolean found = false;
        try {
            // We have to search through all the methods since the
            // types may not match up.
            Method[] methods = node.getClass().getMethods();

            for (int counter = methods.length - 1; counter >= 0; counter--) {
                if (methods[counter].getName()
                        .equals(setterMethodNames[column])
                        && methods[counter].getParameterTypes() != null
                        && methods[counter].getParameterTypes().length == 1) {
                    // We found a matching method
                    Class param = methods[counter].getParameterTypes()[0];
                    if (!param.isInstance(aValue)) {
                        // Yes, we can use the value passed in directly,
                        // no coercision is necessary!
                        if (aValue instanceof String
                                && ((String) aValue).length() == 0) {
                            // Assume an empty string is null, this is
                            // probably bogus for here.
                            aValue = null;
                        } else {
                            // Have to attempt some sort of coercision.
                            // See if the expected parameter type has
                            // a constructor that takes a String.
                            Constructor cs = param
                                    .getConstructor(new Class[] { String.class });
                            if (cs != null) {
                                aValue = cs
                                        .newInstance(new Object[] { aValue });
                            } else {
                                aValue = null;
                            }
                        }
                    }
                    // null either means it was an empty string, or there
                    // was no translation. Could potentially deal with these
                    // differently.
                    methods[counter].invoke(node, new Object[] { aValue });
                    found = true;
                    break;
                }
            }
        } catch (Throwable th) {
            System.out.println("exception: " + th);
        }
        if (found) {
            // The value changed, fire an event to notify listeners.
            TreeNode parent = ((TreeNode) node).getParent();
            fireTreeNodesChanged(this, getPathToRoot(parent),
                    new int[] { getIndexOfChild(parent, node) },
                    new Object[] { node });
        }
    }

    /**
     * Builds the parents of the node up to and including the root node, where
     * the original node is the last element in the returned array. The length
     * of the returned array gives the node's depth in the tree.
     *
     * @param aNode
     *            the TreeNode to get the path for
     * @param an
     *            array of TreeNodes giving the path from the root to the
     *            specified node.
     */
    public TreeNode[] getPathToRoot(TreeNode aNode) {
        return getPathToRoot(aNode, 0);
    }

    /**
     * Builds the parents of the node up to and including the root node, where
     * the original node is the last element in the returned array. The length
     * of the returned array gives the node's depth in the tree.
     *
     * @param aNode
     *            the TreeNode to get the path for
     * @param depth
     *            an int giving the number of steps already taken towards the
     *            root (on recursive calls), used to size the returned array
     * @return an array of TreeNodes giving the path from the root to the
     *         specified node
     */
    private TreeNode[] getPathToRoot(TreeNode aNode, int depth) {
        TreeNode[] retNodes;
        // This method recurses, traversing towards the root in order
        // size the array. On the way back, it fills in the nodes,
        // starting from the root and working back to the original node.

        /*
         * Check for null, in case someone passed in a null node, or they passed
         * in an element that isn't rooted at root.
         */
        if (aNode == null) {
            if (depth == 0) {
                return null;
            } else {
                retNodes = new TreeNode[depth];
            }
        } else {
            depth++;
            if (aNode == root){
                retNodes = new TreeNode[depth];
            }
            else{
                retNodes = getPathToRoot(aNode.getParent(), depth);
            }
            retNodes[retNodes.length - depth] = aNode;
        }
        return retNodes;
    }

    @Override
    public int getColumnWidth(int column) {
        return (this.columnWidth == null) ? 1 : this.columnWidth[column];
    }
}
